<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>18.3.3-将组件渲染为 HTML 字符串</title>
</head>
<body>
    
</body>
    <script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
    <script>
        /**
         * 执行 setup 函数时，也应该提供 setupContext 对象。
         *   而执行渲染函数render 时，也应该将其 this 指向 renderContext 对象。
         *   实际上，在组件的初始化和渲染方面，其完整流程与第13章讲解的客户端的渲染流程一致。
         *   例如，也需要初始化 data，也需要得到 setup 函数的执行结果，
         *    并检查 setup 函数的返回值是函数还是 setupState 等。
         * 
         * 在进行服务端渲染时，组件的初始化流程与客户端渲染时组件的初始化流程基本一致，但有两个重要的区别。
         * - 服务端渲染的是应用的当前快照，它不存在数据变更后重新渲染的情况。
         *    因此，所有数据在服务端都无须是响应式的。
         * - 服务端渲染只需要获取组件要渲染的 subTree 即可，无须调用渲染器完成真实 DOM 的创建。
         *    因此，在服务端渲染时，可以忽略“设置 render effect 完成渲染”这一步。
         * 
         * 由于组件在服务端渲染时，不需要渲染真实 DOM 元素，所以无须创建并执行 render effect
         *  意味着，组件的beforeMount 以及 mounted 钩子不会被触发
         * 由于服务端渲染不存在数据变更后的重新渲染逻辑
         *  所以 beforeUpdate 和 updated 钩子也不会在服务端执行
         * 
         * 在服务端渲染时，无须使用 reactive 函数为 data 数据创建响应式版本，
         *  并且 props 数据也无须是浅响应的
         * 
        */

        const {  shallowReadonly } = VueReactivity; // 新增
        

        // 应该忽略的属性
        const shouldIgnoreProp = ['key', 'ref']

        function renderAttrs(props) {
            let ret = ''
            for (const key in props) {
                if (
                    // 检测属性名称，如果是事件或应该被忽略的属性，则忽略它
                    shouldIgnoreProp.includes(key) ||
                    /^on[^a-z]/.test(key)
                ) {
                    continue
                }
                const value = props[key]
                // 调用 renderDynamicAttr 完成属性的渲染
                ret += renderDynamicAttr(key, value)
            }
            return ret
        }

        // 用来判断属性是否是 boolean attribute
        const isBooleanAttr = (key) =>
            (`itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly` +
            `,async,autofocus,autoplay,controls,default,defer,disabled,hidden,` +
            `loop,open,required,reversed,scoped,seamless,` +
            `checked,muted,multiple,selected`).split(',').includes(key)

        // 用来判断属性名称是否合法且安全
        const isSSRSafeAttrName = (key) => !/[>/="'\u0009\u000a\u000c\u0020]/.test(key)

        function renderDynamicAttr(key, value) {
            if (isBooleanAttr(key)) {
                // 对于 boolean attribute，如果值为 false，则什么都不需要渲染，否则只需要渲染 key 即可
                return value === false ? `` : ` ${key}`
            } else if (isSSRSafeAttrName(key)) {
                // 对于其他安全的属性，执行完整的渲染，
                // 注意：对于属性值，我们需要对它执行 HTML 转义操作
                return value === '' ? ` ${key}` : ` ${key}="${escapeHtml(value)}"`
            } else {
                // 跳过不安全的属性，并打印警告信息
                console.warn(
                    `[@vue/server-renderer] Skipped rendering unsafe attribute name: ${key}`
                )
                return ``
            }
        }

        const escapeRE = /["'&<>]/ 
        function escapeHtml(string) {
            const str = '' + string
            const match = escapeRE.exec(str)

            if (!match) {
                return str
            }

            let html = ''
            let escaped
            let index
            let lastIndex = 0
            for (index = match.index; index < str.length; index++) {
                switch (str.charCodeAt(index)) {
                    case 34: // "
                        escaped = '&quot;'
                        break
                    case 38: // &
                        escaped = '&amp;'
                        break
                    case 39: // '
                        escaped = '&#39;'
                        break
                    case 60: // <
                        escaped = '&lt;'
                        break
                    case 62: // >
                        escaped = '&gt;'
                        break
                    default:
                        continue
                }

                if (lastIndex !== index) {
                    html += str.substring(lastIndex, index)
                }

                lastIndex = index + 1
                html += escaped
            }

            return lastIndex !== index ? html + str.substring(lastIndex, index) : html
        }




        
        const VOID_TAGS = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'.split(',')
        // 在不考虑任何边界条件的情况下，实现 renderElementVNode
        function renderElementVNode(vnode){
            // 取出标签名称 tag 和标签属性 props，以及标签的子节点
            const { type: tag, props, children } = vnode
            // 判断是否是 void element
            const isVoidElement = VOID_TAGS.includes(tag)
            // 开始标签的头部
            let ret = `<${tag}`
            // 处理标签属性
            if (props) {
                // 调用 renderAttrs 函数进行严谨处理
                ret += renderAttrs(props)
            }

            // 如果是 void element，则自闭合
            ret += isVoidElement ? `/>` : `>` 
            // 如果是 void element，则直接返回结果，无须处理 children，因为 void element 没有 children
            if (isVoidElement) return ret 

            // 处理子节点
            // 如果子节点的类型是字符串，则是文本内容，直接拼接
            if (typeof children === 'string') {
                ret += children
            } else if (Array.isArray(children)) {
                // 如果子节点的类型是数组，则递归地调用 renderElementVNode 完成渲染
                children.forEach(child => {
                    ret += renderElementVNode(child)
                })
            }

            // 结束标签
            ret += `</${tag}>`

            // 返回拼接好的 HTML 字符串
            return ret
        }

        // function renderComponentVNode(vnode) {
        //     // 获取 setup 组件选项
        //     let { type: { setup } } = vnode
        //     // 执行 setup 函数得到渲染函数 render
        //     const render = setup()
        //     // 执行渲染函数得到 subTree，即组件要渲染的内容
        //     const subTree = render()
        //     // 调用 renderVNode 完成渲染，并返回其结果
        //     return renderVNode(subTree) // 新增
        // }

        function renderComponentVNode(vnode) { // 新增 renderComponentVNode
            const isFunctional = typeof vnode.type === 'function'
            let componentOptions = vnode.type
            if (isFunctional) {
                componentOptions = {
                    render: vnode.type,
                    props: vnode.type.props
                }
            }
            let { render, data, setup, beforeCreate, created, props: propsOption } = componentOptions

            beforeCreate && beforeCreate()

            // 无须使用 reactive() 创建 data 的响应式版本
            const state = data ? data() : null
            const [props, attrs] = resolveProps(propsOption, vnode.props)

            const slots = vnode.children || {}

            const instance = {
                state,
                props, // props 无须 shallowReactive
                isMounted: false,
                subTree: null,
                slots,
                mounted: [],
                keepAliveCtx: null
            }

            function emit(event, ...payload) {
                const eventName = `on${event[0].toUpperCase() + event.slice(1)}`
                const handler = instance.props[eventName]
                if (handler) {
                    handler(...payload)
                } else {
                    console.error('事件不存在')
                }
            }

            // setup
            let setupState = null
            if (setup) {
                const setupContext = { attrs, emit, slots }
                const prevInstance = setCurrentInstance(instance)
                const setupResult = setup(shallowReadonly(instance.props), setupContext)
                setCurrentInstance(prevInstance)
                if (typeof setupResult === 'function') {
                    if (render) console.error('setup 函数返回渲染函数，render 选项将被忽略')
                    render = setupResult
                } else {
                    setupState = setupContext
                }
            }

            vnode.component = instance

            const renderContext = new Proxy(instance, {
                get(t, k, r) {
                    const { state, props, slots } = t

                    if (k === '$slots') return slots

                    if (state && k in state) {
                        return state[k]
                    } else if (k in props) {
                        return props[k]
                    } else if (setupState && k in setupState) {
                        return setupState[k]
                    } else {
                        console.error('不存在')
                    }
                },
                set (t, k, v, r) {
                    const { state, props } = t
                    if (state && k in state) {
                        state[k] = v
                    } else if (k in props) {
                        props[k] = v
                    } else if (setupState && k in setupState) {
                        setupState[k] = v
                    } else {
                        console.error('不存在')
                    }
                }
            })

            created && created.call(renderContext)

            const subTree = render.call(renderContext, renderContext)

            return renderVNode(subTree)
        }

        /**
         * resolveProps 函数用于解析组件 props 和 attrs 数据
         * @param {Object} options 组件的 props 选项
         * @param {Object} propsData 为组件传递的 props 数据
         * */ 
        function resolveProps(options = {}, propsData = {}) { // 新增 resolveProps
            const props = {}
            const attrs = {}
            // 遍历为组件传递的 props 数据
            for (const key in propsData) {
                // 以字符串 on 开头的 props，无论是否显式地声明，
                // 都将其添加到 props 数据中，而不是添加到 attrs 中
                if (key in options || key.startsWith('on')) {
                    // 如果为组件传递的 props 数据在组件自身的 props 选项中有定义，则将其视为合法的 props
                    props[key] = propsData[key]
                } else {
                    // 否则将其作为 attrs
                    attrs[key] = propsData[key]
                }
            }
            // 最后返回 props 与 attrs 数据
            return [ props, attrs ]
        }

        // 全局变量，存储当前正在被初始化的组件实例
        let currentInstance = null
        // 该方法接收组件实例作为参数，并将该实例设置为 currentInstance
        function setCurrentInstance(instance) { // 新增 setCurrentInstance
            currentInstance = instance
        }

        function renderVNode(vnode) { // 新增 renderVNode
            const type = typeof vnode.type
            if (type === 'string') {
                return renderElementVNode(vnode)
            } else if (type === 'object' || type === 'function') {
                return renderComponentVNode(vnode)
            } else if (vnode.type === Text) {
                // 处理文本...
            } else if (vnode.type === Fragment) {
                // 处理片段...
            } else {
                // 其他 VNode 类型
            }
        }
        // 组件
        const MyComponent = {
            setup() {
                return () => {
                    // 该组件渲染一个 div 标签
                    return {
                        type: 'div',
                        children: 'hello'
                    }
                }
            }
        }

        // 用来描述组件的 VNode 对象
        const CompVNode = {
            type: MyComponent,
        }


        
        
        // 测试
        console.log(renderComponentVNode(CompVNode))
    </script>
</html>